package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"inventory/api/inventory"
	"inventory/internal/constant"
	"inventory/pkg/ecode"
	"inventory/pkg/log"
	"inventory/pkg/network"
	"inventory/pkg/request_key"
	"inventory/pkg/util"
	"strconv"
	"time"

	"github.com/pkg/errors"
	"gorm.io/gorm"

	"inventory/internal/model"
)

// recoverProcessStorage 履约单库存还原操作
func (s *Service) recoverProcessStorage(ctx context.Context, order *model.ProcessOrder, now time.Time) (err error) {
	var (
		processDetails []*model.ProcessDetail
		storages       []*model.Storage
		freezeOrder    *model.FreezeOrder
	)
	if freezeOrder, err = s.dao.GetFreezeOrderByID(ctx, uint64(order.FreezeOrderID.Int64)); err != nil {
		return
	}
	switch freezeOrder.Status {
	case constant.FreezeOrderInit, constant.FreezeOrderCancelled:
		err = constant.ErrInvalidFreezeOrder
		return
	}
	if processDetails, err = s.dao.FindProcessOrderDetails(ctx, order.ID); err != nil {
		return
	}
	storageKeyMap := make(util.StringBoolMap)
	storageQuantityMap := make(map[string]float64)
	recoverMap := make(map[string]float64)
	for _, d := range processDetails {
		if d.IsAccounting {
			storageKeyMap[d.StorageKey] = true
			if _, ok := storageQuantityMap[d.StorageKey]; !ok {
				storageQuantityMap[d.StorageKey] = 0
			}
			// 这里需要聚合物料
			storageQuantityMap[d.StorageKey] -= d.Quantity
		}
		recoverMap[d.StorageKey] = d.Quantity
	}
	storageMap := make(model.StorageMap)
	if storages, err = s.dao.FindWarehouseStoragesByStorageKeys(ctx, order.WarehouseID, storageKeyMap.GetKeys()); err != nil {
		return
	}
	storageMap.InitStorageMap(storages)
	affectedMaterialIDMap := make(util.IntBoolMap)
	for key, quantity := range storageQuantityMap {
		if _, ok := storageMap[key]; ok {
			if _, err = s.dao.DecreaseAndUnfreezeStorage(ctx, order.WarehouseID, key, quantity, now); err != nil {
				return
			}
			if inventory.StorageType(storageMap[key].StorageType) == inventory.StorageType_MATERIAL {
				materialID, _ := strconv.Atoi(key[2:])
				affectedMaterialIDMap[materialID] = true
			}
		} else {
			delete(storageKeyMap, key)
		}
	}
	// 关闭冻结单
	if err = s.recoverFreezeOrder(ctx, freezeOrder, order, recoverMap, now); err != nil {
		return
	}
	// 由于恢复库存均为冻结库存，不会改变前端可售剩余量，故不需要NSQ库存更新通知
	return
}

// DadaNotify 达达回调通知
func (s *Service) DaDaNotify(ctx context.Context, req *inventory.DaDaNotifyRequest) (resp *inventory.DefaultResp, err error) {
	var expressOrderStatus inventory.DeliverStatus
	var processOrderStatus inventory.ProcessOrderStatus
	// 待接单＝1,待取货＝2,配送中＝3,已完成＝4,已取消＝5, 指派单=8,妥投异常之物品返回中=9, 妥投异常之物品返回完成=10, 骑士到店=100,创建达达运单失败=1000
	switch req.OrderStatus {
	// 已取消
	case 1:
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderPending
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
	case 2:
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderWaitPickup
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
	case 3:
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderDelivering
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
	case 4:
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderReceived
		// 注意只有用户签收才能迁为订单已签收，这里保持配送中状态
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
	case 100:
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderPickup
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
	case 1000, 200, 5, 7, 8, 9, 10:
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderFailed
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderWaitShip
	default:
		logger := log.FromContext(ctx)
		logger.Infof("达达推送未知状态：OrderStatus %d", req.OrderStatus)
		return
	}
	resp = &inventory.DefaultResp{Status: true}
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		updateTime := time.Unix(req.UpdateTime, 0)
		// 更新配送单状态
		expressOrder := model.ExpressOrder{OutTradeNo: sql.NullString{
			String: req.OrderId,
			Valid:  true,
		}}
		if err = s.dao.Lock(ctx, expressOrder, &expressOrder); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = nil
				log.FromContext(ctx).Error("达达推送订单%s不在系统中", req.OrderId)
				return
			}
			return
		}
		if updateTime.Before(expressOrder.UpdateTime) || inventory.DeliverStatus(expressOrder.Status) == expressOrderStatus {
			// 推送时间小于更新时间 & 状态与本地一致时，跳过
			return
		}
		order := model.ProcessOrder{OrderNumber: expressOrder.OrderNumber}
		if err = s.dao.Lock(ctx, order, &order); err != nil {
			return
		}
		switch inventory.ProcessOrderStatus(order.Status) {
		case inventory.ProcessOrderStatus_ProcessOrderReceived, inventory.ProcessOrderStatus_ProcessOrderCancelled:
			// 订单终态时，不接受推送
			return
		}
		if !order.ExpressOrderID.Valid || uint64(order.ExpressOrderID.Int64) != expressOrder.ID {
			// 当前订单所关联配送单已丢弃，无需继续更新
			return
		}
		// 开始同步流程
		var (
			platform   *model.ExpressPlatform
			needNotify bool
		)
		if platform, err = s.dao.GetExpressPlatform(ctx, expressOrder.PlatformID); err != nil {
			return
		}
		expressOrder.OrderState = strconv.Itoa(int(req.OrderStatus))
		expressOrder.UpdateTime = updateTime
		if inventory.DeliverStatus(expressOrder.Status) == inventory.DeliverStatus_ExpressOrderCalling {
			// 正在呼叫，未完成后置事务
			if inventory.ProcessOrderStatus(order.Status) != inventory.ProcessOrderStatus_ProcessOrderWaitShip {
				err = fmt.Errorf("达达推送时快递单&履约单状态不一致，需要排查")
				return
			}
			if expressOrderStatus == inventory.DeliverStatus_ExpressOrderFailed {
				// 未后处理时失败，直接失败，履约单无需补偿扣库和更新
				expressOrder.Status = uint8(inventory.DeliverStatus_ExpressOrderFailed)
				expressOrder.Reason = sql.NullString{
					String: req.CancelReason,
					Valid:  true,
				}
				if err = s.dao.Save(ctx, expressOrder); err != nil {
					return
				}
				return
			}
			//未后处理时接单（后置事务未及时调用&异常），此时要补偿扣库步骤&消息
			// 补偿扣库
			expressOrder.Status = uint8(inventory.DeliverStatus_ExpressOrderPending)
			expressOrder.TradeNo = req.ClientId
			order.Status = uint8(inventory.ProcessOrderStatus_ProcessOrderShipped)
			needNotify = true
			if err = s.dao.Save(ctx, expressOrder); err != nil {
				return
			}
			var prepareTime string
			if order.PrepareTime.Valid {
				prepareTime = order.PrepareTime.Time.Format(util.DefaultLayout)
			}
			if err = deliverProcessStorage(ctx, s.dao, &order, &inventory.InventoryOrderUpdatePayload_DeliverMemo{
				Platform:         platform.Name,
				PlatformOrderNum: expressOrder.TradeNo,
				DeliverTime:      updateTime.Format(util.DefaultLayout),
				PrepareTime:      prepareTime,
			}, updateTime, false); err != nil {
				return
			}
		} else {
			expressOrder.Status = uint8(expressOrderStatus)
			expressOrder.TradeNo = req.ClientId
			if order.Status != uint8(processOrderStatus) {
				needNotify = true
			}
			order.Status = uint8(processOrderStatus)
			order.UpdateTime = updateTime
		}
		switch expressOrderStatus {
		case inventory.DeliverStatus_ExpressOrderWaitPickup, inventory.DeliverStatus_ExpressOrderPickup, inventory.DeliverStatus_ExpressOrderDelivering:
			// 需要更新骑手信息
			expressOrder.RiderName = req.DmName
			expressOrder.RiderPhone = req.DmMobile
		case inventory.DeliverStatus_ExpressOrderFailed:
			// 配送失败, 需要还原库存&冻结单
			expressOrder.Reason = sql.NullString{
				String: req.CancelReason,
				Valid:  true,
			}
			order.DeliverTime = util.NullTime{}
			if err = s.recoverProcessStorage(ctx, &order, updateTime); err != nil {
				return
			}
		}
		if err = s.dao.Save(ctx, expressOrder); err != nil {
			return
		}
		if err = s.dao.Save(ctx, order); err != nil {
			return
		}
		if needNotify {
			// NSQ通知
			memo := &inventory.InventoryOrderUpdatePayload_DeliverMemo{}
			var prepareTime string
			if order.PrepareTime.Valid {
				prepareTime = order.PrepareTime.Time.Format(util.DefaultLayout)
			}
			if expressOrderStatus != inventory.DeliverStatus_ExpressOrderFailed {
				memo.Platform = platform.Name
				memo.PlatformOrderNum = expressOrder.TradeNo
				memo.DeliverTime = order.DeliverTime.Time.Format(util.DefaultLayout)
				memo.PrepareTime = prepareTime
			}
			statsMsg, _ := json.Marshal(&inventory.InventoryOrderUpdatePayload{
				OrderNumber:    order.OrderNumber,
				ExtOrderNumber: order.ExtOrderNumber,
				Source:         inventory.SourceEnum(order.Source),
				Timestamp:      updateTime.Unix(),
				Status:         inventory.ProcessOrderStatus(order.Status),
				DeliverType:    inventory.DeliverType(order.DeliverType),
				DeliverMemo:    memo,
			})
			if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicInventoryStatus, statsMsg); err != nil {
				return err
			}
		}
		return
	})
	return
}

// SFNotify 顺丰通知
func (s *Service) SFNotify(ctx context.Context, req *inventory.SFNotifyRequest) (reply *inventory.SFNotifySuccessReply, err error) {
	// 顺丰签名目前在pos校验
	rawBody := request_key.GetSingleMetaData(ctx, network.HttpRawBody, "")
	if rawBody == "" {
		reply = &inventory.SFNotifySuccessReply{
			ErrorCode: 430,
			ErrorMsg:  "签名错误",
		}
		return
	} else if !s.dao.SFNotifyVerify(req.Sign, rawBody) {
		err = ecode.BadRequest("顺丰签名错误")
		reply = &inventory.SFNotifySuccessReply{
			ErrorCode: 430,
			ErrorMsg:  "签名错误",
		}
		return
	}
	reply = &inventory.SFNotifySuccessReply{
		ErrorCode: 0,
		ErrorMsg:  "success",
	}
	var expressOrderStatus inventory.DeliverStatus
	var processOrderStatus inventory.ProcessOrderStatus

	switch req.UrlIndex {
	case "rider_status":
		switch req.OrderStatus {
		case 10: // 配送员确认
			expressOrderStatus = inventory.DeliverStatus_ExpressOrderWaitPickup
			processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
		case 12:
			expressOrderStatus = inventory.DeliverStatus_ExpressOrderPickup
			processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
		case 14, 15: // 配送中
			expressOrderStatus = inventory.DeliverStatus_ExpressOrderDelivering
			processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
		default:
			// 其他推送状态不同步
			return
		}
	case "order_complete":
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderReceived
		// 注意只有用户签收才能迁为订单已签收，这里保持配送中状态
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderShipped
	case "dubious_reject":
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderFailed
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderWaitShip
	case "sf_cancel":
		expressOrderStatus = inventory.DeliverStatus_ExpressOrderFailed
		processOrderStatus = inventory.ProcessOrderStatus_ProcessOrderWaitShip
	default:
		// 其他推送状态不同步
		return
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		// 校验数据一致性
		updateTime := time.Unix(req.PushTime, 0)
		expressOrder := model.ExpressOrder{OutTradeNo: sql.NullString{
			String: req.ShopOrderId,
			Valid:  true,
		}}
		if err = s.dao.Lock(ctx, expressOrder, &expressOrder); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				// 推送系统不存在的单，丢弃
				log.FromContext(ctx).Error("顺丰推送订单%s不在系统中", req.ShopOrderId)
				err = nil
				return
			}
			return
		}
		if updateTime.Before(expressOrder.UpdateTime) || inventory.DeliverStatus(expressOrder.Status) == expressOrderStatus {
			// 推送时间小于更新时间 & 状态与本地一致时，跳过
			return
		}
		order := model.ProcessOrder{OrderNumber: expressOrder.OrderNumber}
		if err = s.dao.Lock(ctx, order, &order); err != nil {
			return
		}
		switch inventory.ProcessOrderStatus(order.Status) {
		case inventory.ProcessOrderStatus_ProcessOrderReceived, inventory.ProcessOrderStatus_ProcessOrderCancelled:
			// 订单终态时，不接受推送
			return
		}
		if !order.ExpressOrderID.Valid || uint64(order.ExpressOrderID.Int64) != expressOrder.ID {
			// 当前订单所关联配送单已丢弃，无需继续更新
			return
		}
		// 开始同步流程
		var (
			platform   *model.ExpressPlatform
			needNotify bool
		)
		if platform, err = s.dao.GetExpressPlatform(ctx, expressOrder.PlatformID); err != nil {
			return
		}
		expressOrder.UpdateTime = updateTime
		expressOrder.OrderState = strconv.Itoa(int(req.OrderStatus))
		if inventory.DeliverStatus(expressOrder.Status) == inventory.DeliverStatus_ExpressOrderCalling {
			// 正在呼叫，未完成后置事务
			if inventory.ProcessOrderStatus(order.Status) != inventory.ProcessOrderStatus_ProcessOrderWaitShip {
				err = fmt.Errorf("顺丰推送时快递单&履约单状态不一致，需要排查")
				return
			}
			if expressOrderStatus == inventory.DeliverStatus_ExpressOrderFailed {
				// 未后处理时失败，直接失败，履约单无需补偿扣库和更新
				expressOrder.Status = uint8(inventory.DeliverStatus_ExpressOrderFailed)
				expressOrder.Reason = sql.NullString{
					String: req.CancelReason,
					Valid:  true,
				}
				if err = s.dao.Save(ctx, expressOrder); err != nil {
					return
				}
				return
			}
			//未后处理时接单（后置事务未及时调用&异常），此时要补偿扣库步骤&消息
			// 补偿扣库
			expressOrder.Status = uint8(inventory.DeliverStatus_ExpressOrderPending)
			expressOrder.TradeNo = req.SfOrderId
			order.Status = uint8(inventory.ProcessOrderStatus_ProcessOrderShipped)
			needNotify = true
			if err = s.dao.Save(ctx, expressOrder); err != nil {
				return
			}
			var prepareTime string
			if order.PrepareTime.Valid {
				prepareTime = order.PrepareTime.Time.Format(util.DefaultLayout)
			}
			if err = deliverProcessStorage(ctx, s.dao, &order, &inventory.InventoryOrderUpdatePayload_DeliverMemo{
				Platform:         platform.Name,
				PlatformOrderNum: expressOrder.TradeNo,
				DeliverTime:      updateTime.Format(util.DefaultLayout),
				PrepareTime:      prepareTime,
			}, updateTime, false); err != nil {
				return
			}
		} else {
			expressOrder.Status = uint8(expressOrderStatus)
			expressOrder.TradeNo = req.SfOrderId
			if inventory.ProcessOrderStatus(order.Status) != processOrderStatus {
				needNotify = true
			}
			order.Status = uint8(processOrderStatus)
			order.UpdateTime = updateTime
		}

		switch expressOrderStatus {
		case inventory.DeliverStatus_ExpressOrderWaitPickup, inventory.DeliverStatus_ExpressOrderPickup, inventory.DeliverStatus_ExpressOrderDelivering:
			// 需要更新骑手信息
			expressOrder.RiderName = req.OperatorName
			expressOrder.RiderPhone = req.OperatorPhone
		case inventory.DeliverStatus_ExpressOrderFailed:
			// 配送失败, 需要还原库存&冻结单
			expressOrder.Reason = sql.NullString{
				String: req.CancelReason,
				Valid:  true,
			}
			order.DeliverTime = util.NullTime{}
			if err = s.recoverProcessStorage(ctx, &order, updateTime); err != nil {
				return
			}
		}
		if err = s.dao.Save(ctx, expressOrder); err != nil {
			return
		}
		if err = s.dao.Save(ctx, order); err != nil {
			return
		}
		if needNotify {
			// NSQ通知
			memo := &inventory.InventoryOrderUpdatePayload_DeliverMemo{}
			var prepareTime string
			if order.PrepareTime.Valid {
				prepareTime = order.PrepareTime.Time.Format(util.DefaultLayout)
			}
			if expressOrderStatus != inventory.DeliverStatus_ExpressOrderFailed {
				memo.Platform = platform.Name
				memo.PlatformOrderNum = expressOrder.TradeNo
				memo.DeliverTime = order.DeliverTime.Time.Format(util.DefaultLayout)
				memo.PrepareTime = prepareTime
			}
			statsMsg, _ := json.Marshal(&inventory.InventoryOrderUpdatePayload{
				OrderNumber:    order.OrderNumber,
				ExtOrderNumber: order.ExtOrderNumber,
				Source:         inventory.SourceEnum(order.Source),
				Timestamp:      updateTime.Unix(),
				Status:         inventory.ProcessOrderStatus(order.Status),
				DeliverType:    inventory.DeliverType(order.DeliverType),
				DeliverMemo:    memo,
			})
			if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicInventoryStatus, statsMsg); err != nil {
				return err
			}
		}
		return
	})
	return
}
